/*
# pymoz, Python extension for the Mozilla Toolkit
# Copyright (C) 2012 Malek Hadj-Ali
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include "pyWrappers.h"


#define JS_ClassName(jsobj) JS_GetClass(jsobj)->name


#define JS_FN_CHECK_NARGS(name, nargs, argc) \
    do { \
        if (argc != nargs) { \
            PyErr_Format(PyExc_TypeError, \
                         "%s() takes exactly %u argument%s (%u given)", \
                         name, nargs, nargs != 1 ? "s" : "", argc); \
            pyRuntime::ReportError(); \
            return JS_FALSE; \
        } \
    } while (0)


static JSObject *gJSPyTypeProto = NULL;


/* fwd */
static JSObject *
JSPyObject_New(JSContext *cx, PyObject *pyobj);


/*******************************************************************************
 utils
*******************************************************************************/

static PyObject *
PyArgs_FromJSArgs(JSContext *cx, unsigned argc, jsval *argv)
{
    PyObject *pyarg = NULL;
    unsigned i;

    PyObject *pyargs = PyTuple_New(argc);
    if (pyargs && argc) {
        for (i = 0; i < argc; i++) {
            pyarg = PyObject_FromJSVal(cx, argv[i]);
            if (!pyarg) {
                Py_DECREF(pyargs);
                return NULL;
            }
            PyTuple_SET_ITEM(pyargs, i, pyarg);
        }
    }
    return pyargs;
}


static bool
PyType_IsMapping(PyTypeObject *type)
{
    return (type->tp_as_mapping && type->tp_as_mapping->mp_subscript);
}


static bool
PyType_IsSequence(PyTypeObject *type)
{
    return (type->tp_as_sequence && type->tp_as_sequence->sq_item);
}


static bool
PyType_IsIter(PyTypeObject *type)
{
    return (type->tp_iternext &&
            type->tp_iternext != &_PyObject_NextNotImplemented);
}


/*******************************************************************************
 JSPy*Class members helpers
*******************************************************************************/

/* JSPy*Class.delProperty */

static JSBool
JSPyObject_DelProperty(JSContext *cx, JSHandleObject self, JSHandleId id, jsval *vp)
//JSPyObject_DelProperty(JSContext *cx, JSObject *self, jsid id, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;

    JSObject *proto = JS_GetPrototype(self);
    JSBool found;
    if (JS_HasPropertyById(cx, proto, id, &found)) {
        if (found) {
            result = JS_TRUE;
        }
        else {
            PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
            PyObject *pyname = JSString_Decode(cx, JSID_TO_STRING(id));
            if (pyname) {
                result = !PyObject_DelAttr(pyobj, pyname);
                Py_DECREF(pyname);
            }
        }
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* JSPy*Class.getProperty */
static JSBool
JSPyObject_GetProperty(JSContext *cx, JSHandleObject self, JSHandleId id, jsval *vp)
//JSPyObject_GetProperty(JSContext *cx, JSObject *self, jsid id, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;

    JSObject *proto = JS_GetPrototype(self);
    JSBool found;
    if (JS_HasPropertyById(cx, proto, id, &found)) {
        if (found) {
            result = JS_TRUE;
        }
        else {
            PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
            PyObject *pyname = JSString_Decode(cx, JSID_TO_STRING(id));
            if (pyname) {
                PyObject *pyresult = PyObject_GetAttr(pyobj, pyname);
                if (pyresult) {
                    *vp = PyObject_AsJSVal(cx, pyresult);
                    Py_DECREF(pyresult);
                    if (!JSVAL_IS_VOID(*vp)) {
                        result = JS_TRUE;
                    }
                }
                Py_DECREF(pyname);
            }
        }
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* JSPy*Class.setProperty */
static JSBool
JSPyObject_SetProperty(JSContext *cx, JSHandleObject self, JSHandleId id, JSBool strict, jsval *vp)
//JSPyObject_SetProperty(JSContext *cx, JSObject *self, jsid id, JSBool strict, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;

    JSObject *proto = JS_GetPrototype(self);
    JSBool found;
    if (JS_HasPropertyById(cx, proto, id, &found)) {
        if (found) {
            result = JS_TRUE;
        }
        else {
            PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
            PyObject *pyname = JSString_Decode(cx, JSID_TO_STRING(id));
            if (pyname) {
                PyObject *pyvalue = PyObject_FromJSVal(cx, *vp);
                if (pyvalue) {
                    result = !PyObject_SetAttr(pyobj, pyname, pyvalue);
                    Py_DECREF(pyvalue);
                }
                Py_DECREF(pyname);
            }
        }
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* JSPy*Class.finalize */
static void
JSPyObject_Finalize(JSFreeOp *fop, JSObject *self)
{
    PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
    if (pyobj) {
        Py_DECREF(pyobj);
        JS_SetPrivate(self, NULL);
    }
}


/* JSPy*Class.call and JSPy*Class.construct */
static JSBool
JSPyObject_Call(JSContext *cx, unsigned argc, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;
    PyObject *pyresult = NULL;

    PyObject *pyargs = PyArgs_FromJSArgs(cx, argc, JS_ARGV(cx, vp));
    if (pyargs) {
        JSObject *self = JSVAL_TO_OBJECT(JS_CALLEE(cx, vp));
        pyresult = PyObject_Call((PyObject *)JS_GetPrivate(self), pyargs, NULL);
        Py_DECREF(pyargs);
    }
    if (pyresult) {
        jsval value = PyObject_AsJSVal(cx, pyresult);
        if (!JSVAL_IS_VOID(value)) {
            JS_SET_RVAL(cx, vp, value);
            result = JS_TRUE;
        }
        Py_DECREF(pyresult);
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* JSPy*Class.hasInstance */
static JSBool
JSPyObject_HasInstance(JSContext *cx, JSHandleObject self, const jsval *v, JSBool *bp)
//JSPyObject_HasInstance(JSContext *cx, JSObject *self, const jsval *v, JSBool *bp)
{
    JSAutoContextPusher acp(cx);
    *bp = JS_FALSE;

    if (!JSVAL_IS_PRIMITIVE(*v)) {
        JSObject *jsobj = JSVAL_TO_OBJECT(*v);
        if (JSPyObject_Check(jsobj)) {
            *bp = PyObject_TypeCheck((PyObject *)JS_GetPrivate(jsobj),
                                     (PyTypeObject *)JS_GetPrivate(self));
        }
    }
    return JS_TRUE;
}


/*******************************************************************************
 JSPyTypeClass - types
*******************************************************************************/

static JSClass JSPyTypeClass = {
    "pyext.JSPyType",                         /*name*/
    JSCLASS_HAS_PRIVATE,                      /*flags*/
    JS_PropertyStub,                          /*addProperty*/
    JSPyObject_DelProperty,                   /*delProperty*/
    JSPyObject_GetProperty,                   /*getProperty*/
    JSPyObject_SetProperty,                   /*setProperty*/
    JS_EnumerateStub,                         /*enumerate*/
    JS_ResolveStub,                           /*resolve*/
    JS_ConvertStub,                           /*convert*/
    JSPyObject_Finalize,                      /*finalize*/
    NULL,                                     /*checkAccess*/
    JSPyObject_Call,                          /*call*/
    JSPyObject_HasInstance,                   /*hasInstance*/
    JSPyObject_Call,                          /*construct*/
};


/* JSPyType *******************************************************************/

/* toSource() */
static JSBool
JSPyType_Repr(JSContext *cx, unsigned argc, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;

    JS_FN_CHECK_NARGS("toSource", 0, argc);
    JSObject *self = JS_THIS_OBJECT(cx, vp);
    //PyObject *pyresult = PyObject_Repr((PyObject *)JS_GetPrivate(self));
    PyObject *pyresult = PyUnicode_FromFormat("[%s at %p wrapping: %R]",
                                              JS_ClassName(self), self,
                                              (PyObject *)JS_GetPrivate(self));
    if (pyresult) {
        jsval value = PyObject_AsJSVal(cx, pyresult);
        if (!JSVAL_IS_VOID(value)) {
            JS_SET_RVAL(cx, vp, value);
            result = JS_TRUE;
        }
        Py_DECREF(pyresult);
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* toString() */
static JSBool
JSPyType_Str(JSContext *cx, unsigned argc, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;

    JS_FN_CHECK_NARGS("toString", 0, argc);
    JSObject *self = JS_THIS_OBJECT(cx, vp);
    PyObject *pyresult = PyObject_Str((PyObject *)JS_GetPrivate(self));
    if (pyresult) {
        jsval value = PyObject_AsJSVal(cx, pyresult);
        if (!JSVAL_IS_VOID(value)) {
            JS_SET_RVAL(cx, vp, value);
            result = JS_TRUE;
        }
        Py_DECREF(pyresult);
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* __iterator__() */
static JSBool
JSPyType_GetIter(JSContext *cx, unsigned argc, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;

    JS_FN_CHECK_NARGS("__iterator__", 1, argc);
    JSObject *self = JS_THIS_OBJECT(cx, vp);
    PyObject *pyresult = PyObject_GetIter((PyObject *)JS_GetPrivate(self));
    if (pyresult) {
        // iterators will not go in the cache
        JSObject *jsiter = JSPyObject_New(cx, pyresult);
        if (jsiter) {
            JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(jsiter));
            result = JS_TRUE;
        }
        Py_DECREF(pyresult);
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;

}


/* JSPyTypeFunctions */
static JSFunctionSpec JSPyTypeFunctions[] = {
    JS_FN("toSource", JSPyType_Repr, 0, JSPROP_READONLY | JSPROP_PERMANENT),
    JS_FN("toString", JSPyType_Str, 0, JSPROP_READONLY | JSPROP_PERMANENT),
    JS_FN("__iterator__", JSPyType_GetIter, 1, JSPROP_READONLY | JSPROP_PERMANENT),
    JS_FS_END
};


/* JSPyIter *******************************************************************/

/* next() */
static JSBool
JSPyIter_Next(JSContext *cx, unsigned argc, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;
    PyObject *pyresult = NULL;

    JS_FN_CHECK_NARGS("next", 0, argc);
    pyresult = PyIter_Next((PyObject *)JS_GetPrivate(JS_THIS_OBJECT(cx, vp)));
    if (pyresult) {
        jsval value = PyObject_AsJSVal(cx, pyresult);
        if (!JSVAL_IS_VOID(value)) {
            JS_SET_RVAL(cx, vp, value);
            result = JS_TRUE;
        }
        Py_DECREF(pyresult);
    }
    if (!result) {
        if (PyErr_Occurred()) {
            pyRuntime::ReportError();
        }
        else {
            JS_ThrowStopIteration(cx);
        }
    }
    return result;
}


/* JSPyIterFunctions */
static JSFunctionSpec JSPyIterFunctions[] = {
    JS_FN("next", JSPyIter_Next, 0, JSPROP_READONLY | JSPROP_PERMANENT),
    JS_FS_END
};


/* JSPySequence ***************************************************************/

/* __iterator__() */
static JSBool
JSPySequence_GetIter(JSContext *cx, unsigned argc, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;
    PyObject *pyresult = NULL;

    JS_FN_CHECK_NARGS("__iterator__", 1, argc);
    JSObject *self = JS_THIS_OBJECT(cx, vp);
    PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
    if (JSVAL_TO_BOOLEAN(JS_ARGV(cx, vp)[0])) {
        Py_ssize_t stop = PySequence_Length(pyobj);
        if (stop >= 0) {
            PyObject *pytmp = PyObject_CallFunction((PyObject *)&PyRange_Type,
                                                    const_cast<char *>("n"),
                                                    stop);
            if (pytmp) {
                pyresult = PyObject_GetIter(pytmp);
                Py_DECREF(pytmp);
            }
        }
    }
    else {
        pyresult = PyObject_GetIter(pyobj);
    }
    if (pyresult) {
        // iterators will not go in the cache
        JSObject *jsiter = JSPyObject_New(cx, pyresult);
        if (jsiter) {
            JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(jsiter));
            result = JS_TRUE;
        }
        Py_DECREF(pyresult);
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* JSPySequenceFunctions */
static JSFunctionSpec JSPySequenceFunctions[] = {
    JS_FN("__iterator__", JSPySequence_GetIter, 1, JSPROP_READONLY | JSPROP_PERMANENT),
    JS_FS_END
};


/* length */
static JSBool
JSPySequence_GetLength(JSContext *cx, JSHandleObject self, JSHandleId id, jsval *vp)
//JSPySequence_GetLength(JSContext *cx, JSObject *self, jsid id, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;

    PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
    Py_ssize_t length = PySequence_Length(pyobj);
    if (length >= 0 && JS_NewNumberValue(cx, length, vp)) {
        result = JS_TRUE;
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* JSPySequenceProperties */
static JSPropertySpec JSPySequenceProperties[] = {
    {"length", 0, JSPROP_SHARED | JSPROP_READONLY | JSPROP_PERMANENT,
     JSPySequence_GetLength, NULL},
    {NULL, 0, 0, NULL, NULL}
};


/* JSPyMapping ****************************************************************/

/* size() */
static JSBool
JSPyMapping_Size(JSContext *cx, unsigned argc, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;

    JS_FN_CHECK_NARGS("size", 0, argc);
    JSObject *self = JS_THIS_OBJECT(cx, vp);
    PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
    Py_ssize_t size = PyMapping_Size(pyobj);
    if (size >= 0) {
        jsval value;
        if (JS_NewNumberValue(cx, size, &value)) {
            JS_SET_RVAL(cx, vp, value);
            result = JS_TRUE;
        }
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* has(key) */
static JSBool
JSPyMapping_HasKey(JSContext *cx, unsigned argc, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;

    JS_FN_CHECK_NARGS("has", 1, argc);
    JSObject *self = JS_THIS_OBJECT(cx, vp);
    PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
    PyObject *pykey = PyObject_FromJSVal(cx, JS_ARGV(cx, vp)[0]);
    if (pykey) {
        jsval value = PyMapping_HasKey(pyobj, pykey) ? JSVAL_TRUE : JSVAL_FALSE;
        JS_SET_RVAL(cx, vp, value);
        result = JS_TRUE;
        Py_DECREF(pykey);
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* get(key) */
static JSBool
JSPyMapping_GetItem(JSContext *cx, unsigned argc, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;
    PyObject *pyresult = NULL;

    JS_FN_CHECK_NARGS("get", 1, argc);
    JSObject *self = JS_THIS_OBJECT(cx, vp);
    PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
    PyObject *pykey = PyObject_FromJSVal(cx, JS_ARGV(cx, vp)[0]);
    if (pykey) {
        pyresult = PyObject_GetItem(pyobj, pykey);
        if (!pyresult && PyErr_ExceptionMatches(PyExc_KeyError)) {
            PyErr_Clear();
            JS_SET_RVAL(cx, vp, JSVAL_VOID);
            result = JS_TRUE;
        }
        Py_DECREF(pykey);
    }
    if (pyresult) {
        jsval value = PyObject_AsJSVal(cx, pyresult);
        if (!JSVAL_IS_VOID(value)) {
            JS_SET_RVAL(cx, vp, value);
            result = JS_TRUE;
        }
        Py_DECREF(pyresult);
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* set(key, value) */
static JSBool
JSPyMapping_SetItem(JSContext *cx, unsigned argc, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;

    JS_FN_CHECK_NARGS("set", 2, argc);
    JSObject *self = JS_THIS_OBJECT(cx, vp);
    PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
    jsval *args = JS_ARGV(cx, vp);
    PyObject *pykey = PyObject_FromJSVal(cx, args[0]);
    PyObject *pyvalue = PyObject_FromJSVal(cx, args[1]);
    if (pykey && pyvalue) {
        result = PyObject_SetItem(pyobj, pykey, pyvalue) ? JS_FALSE : JS_TRUE;
        JS_SET_RVAL(cx, vp, JSVAL_VOID);
    }
    Py_XDECREF(pyvalue);
    Py_XDECREF(pykey);
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* delete(key) */
static JSBool
JSPyMapping_DelItem(JSContext *cx, unsigned argc, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;

    JS_FN_CHECK_NARGS("delete", 1, argc);
    JSObject *self = JS_THIS_OBJECT(cx, vp);
    PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
    PyObject *pykey = PyObject_FromJSVal(cx, JS_ARGV(cx, vp)[0]);
    if (pykey) {
        if (PyObject_DelItem(pyobj, pykey)) {
            if (PyErr_ExceptionMatches(PyExc_KeyError)) {
                PyErr_Clear();
                JS_SET_RVAL(cx, vp, JSVAL_FALSE);
                result = JS_TRUE;
            }
        }
        else {
            JS_SET_RVAL(cx, vp, JSVAL_TRUE);
            result = JS_TRUE;
        }
        Py_DECREF(pykey);
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* __iterator__() */
static JSBool
JSPyMapping_GetIter(JSContext *cx, unsigned argc, jsval *vp)
{
    JSAutoContextPusher acp(cx);
    JSBool result = JS_FALSE;
    PyObject *pyresult = NULL;

    JS_FN_CHECK_NARGS("__iterator__", 1, argc);
    JSObject *self = JS_THIS_OBJECT(cx, vp);
    PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
    if (JSVAL_TO_BOOLEAN(JS_ARGV(cx, vp)[0])) {
        pyresult = PyObject_GetIter(pyobj);
    }
    else {
        PyObject *pytmp = PyMapping_Values(pyobj);
        if (pytmp) {
            pyresult = PyObject_GetIter(pytmp);
            Py_DECREF(pytmp);
        }
    }
    if (pyresult) {
        // iterators will not go in the cache
        JSObject *jsiter = JSPyObject_New(cx, pyresult);
        if (jsiter) {
            JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(jsiter));
            result = JS_TRUE;
        }
        Py_DECREF(pyresult);
    }
    if (!result) {
        pyRuntime::ReportError();
    }
    return result;
}


/* JSPyMappingFunctions */
static JSFunctionSpec JSPyMappingFunctions[] = {
    JS_FN("size", JSPyMapping_Size, 0, JSPROP_READONLY | JSPROP_PERMANENT),
    JS_FN("has", JSPyMapping_HasKey, 1, JSPROP_READONLY | JSPROP_PERMANENT),
    JS_FN("get", JSPyMapping_GetItem, 1, JSPROP_READONLY | JSPROP_PERMANENT),
    JS_FN("set", JSPyMapping_SetItem, 2, JSPROP_READONLY | JSPROP_PERMANENT),
    JS_FN("delete", JSPyMapping_DelItem, 1, JSPROP_READONLY | JSPROP_PERMANENT),
    JS_FN("__iterator__", JSPyMapping_GetIter, 1, JSPROP_READONLY | JSPROP_PERMANENT),
    JS_FS_END
};


/*******************************************************************************
 JSPyCallableClass - object
*******************************************************************************/

static JSClass JSPyCallableClass = {
    "pyext.JSPyCallable",                     /*name*/
    JSCLASS_HAS_PRIVATE,                      /*flags*/
    JS_PropertyStub,                          /*addProperty*/
    JSPyObject_DelProperty,                   /*delProperty*/
    JSPyObject_GetProperty,                   /*getProperty*/
    JSPyObject_SetProperty,                   /*setProperty*/
    JS_EnumerateStub,                         /*enumerate*/
    JS_ResolveStub,                           /*resolve*/
    JS_ConvertStub,                           /*convert*/
    JSPyObject_Finalize,                      /*finalize*/
    NULL,                                     /*checkAccess*/
    JSPyObject_Call,                          /*call*/
};


/*******************************************************************************
 JSPySequenceClass - object
*******************************************************************************/

/* JSPySequenceClass.delProperty */
static JSBool
JSPySequence_DelProperty(JSContext *cx, JSHandleObject self, JSHandleId id, jsval *vp)
//JSPySequence_DelProperty(JSContext *cx, JSObject *self, jsid id, jsval *vp)
{
    if (JSID_IS_INT(id)) {
        JSAutoContextPusher acp(cx);
        JSBool result = JS_FALSE;

        PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
        Py_ssize_t idx = JSID_TO_INT(id);
        result = !PySequence_DelItem(pyobj, idx);
        if (!result) {
            pyRuntime::ReportError();
        }
        return result;
    }
    return JSPyObject_DelProperty(cx, self, id, vp);
}


/* JSPySequenceClass.getProperty */
static JSBool
JSPySequence_GetProperty(JSContext *cx, JSHandleObject self, JSHandleId id, jsval *vp)
//JSPySequence_GetProperty(JSContext *cx, JSObject *self, jsid id, jsval *vp)
{
    if (JSID_IS_INT(id)) {
        JSAutoContextPusher acp(cx);
        JSBool result = JS_FALSE;

        PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
        Py_ssize_t idx = JSID_TO_INT(id);
        PyObject *pyresult = PySequence_GetItem(pyobj, idx);
        if (pyresult) {
            *vp = PyObject_AsJSVal(cx, pyresult);
            Py_DECREF(pyresult);
            if (!JSVAL_IS_VOID(*vp)) {
                result = JS_TRUE;
            }
        }
        if (!result) {
            pyRuntime::ReportError();
        }
        return result;
    }
    return JSPyObject_GetProperty(cx, self, id, vp);
}


/* JSPySequenceClass.setProperty */
static JSBool
JSPySequence_SetProperty(JSContext *cx, JSHandleObject self, JSHandleId id, JSBool strict, jsval *vp)
//JSPySequence_SetProperty(JSContext *cx, JSObject *self, jsid id, JSBool strict, jsval *vp)
{
    if (JSID_IS_INT(id)) {
        JSAutoContextPusher acp(cx);
        JSBool result = JS_FALSE;

        PyObject *pyobj = (PyObject *)JS_GetPrivate(self);
        Py_ssize_t idx = JSID_TO_INT(id);
        PyObject *pyvalue = PyObject_FromJSVal(cx, *vp);
        if (pyvalue) {
            result = !PySequence_SetItem(pyobj, idx, pyvalue);
            Py_DECREF(pyvalue);
        }
        if (!result) {
            pyRuntime::ReportError();
        }
        return result;
    }
    return JSPyObject_SetProperty(cx, self, id, strict, vp);
}


static JSClass JSPySequenceClass = {
    "pyext.JSPySequence",                     /*name*/
    JSCLASS_HAS_PRIVATE,                      /*flags*/
    JS_PropertyStub,                          /*addProperty*/
    JSPySequence_DelProperty,                 /*delProperty*/
    JSPySequence_GetProperty,                 /*getProperty*/
    JSPySequence_SetProperty,                 /*setProperty*/
    JS_EnumerateStub,                         /*enumerate*/
    JS_ResolveStub,                           /*resolve*/
    JS_ConvertStub,                           /*convert*/
    JSPyObject_Finalize,                      /*finalize*/
};


/*******************************************************************************
 JSPyMappingClass - object
*******************************************************************************/

static JSClass JSPyMappingClass = {
    "pyext.JSPyMapping",                      /*name*/
    JSCLASS_HAS_PRIVATE,                      /*flags*/
    JS_PropertyStub,                          /*addProperty*/
    JSPyObject_DelProperty,                   /*delProperty*/
    JSPyObject_GetProperty,                   /*getProperty*/
    JSPyObject_SetProperty,                   /*setProperty*/
    JS_EnumerateStub,                         /*enumerate*/
    JS_ResolveStub,                           /*resolve*/
    JS_ConvertStub,                           /*convert*/
    JSPyObject_Finalize,                      /*finalize*/
};


/*******************************************************************************
 JSPyObjectClass - object
*******************************************************************************/

static JSClass JSPyObjectClass = {
    "pyext.JSPyObject",                       /*name*/
    JSCLASS_HAS_PRIVATE,                      /*flags*/
    JS_PropertyStub,                          /*addProperty*/
    JSPyObject_DelProperty,                   /*delProperty*/
    JSPyObject_GetProperty,                   /*getProperty*/
    JSPyObject_SetProperty,                   /*setProperty*/
    JS_EnumerateStub,                         /*enumerate*/
    JS_ResolveStub,                           /*resolve*/
    JS_ConvertStub,                           /*convert*/
    JSPyObject_Finalize,                      /*finalize*/
};


/*******************************************************************************
 helpers
*******************************************************************************/

static JSObject *
JSPyObject_Create(JSContext *cx, JSClass *clasp, JSObject *proto, PyObject *pyobj)
{

    JSObject *self = JS_NewObject(cx, clasp, proto, NULL);
    if (!self) {
        return NULL;
    }
    Py_INCREF(pyobj);
    JS_SetPrivate(self, pyobj);
    return self;
}


static JSObject *
JSPyType_Create(JSContext *cx, JSClass *clasp, JSObject *proto, PyObject *pyobj,
                JSFunctionSpec *funcs, JSPropertySpec *props)
{

    JSObject *self = JSPyObject_Create(cx, clasp, proto, pyobj);
    if (self) {
        if (funcs && !JS_DefineFunctions(cx, self, funcs)) {
            return NULL;
        }
        if (props && !JS_DefineProperties(cx, self, props)) {
            return NULL;
        }
    }
    return self;
}


static JSObject *
JSPyObject_New(JSContext *cx, PyObject *pyobj)
{
    JSObject *proto = NULL;
    PyTypeObject *pytype = Py_TYPE(pyobj);
    if (pytype == &PyType_Type) {
        proto = gJSPyTypeProto;
    }
    else {
        proto = JSPyObject_NewOrUsed(cx, (PyObject *)pytype);
        if (!proto) {
            return NULL;
        }
    }
    if (PyType_Check(pyobj)) {
        JSFunctionSpec *funcs = NULL;
        JSPropertySpec *props = NULL;
        PyTypeObject *type = (PyTypeObject *)pyobj;
        if (PyType_IsIter(type)) {
            funcs = JSPyIterFunctions;
        }
        else if (PyType_IsSequence(type)) {
            funcs = JSPySequenceFunctions;
            props = JSPySequenceProperties;
        }
        else if (PyType_IsMapping(type)) {
            funcs = JSPyMappingFunctions;
        }
        return JSPyType_Create(cx, &JSPyTypeClass, proto, pyobj, funcs, props);
    }
    if (PyCallable_Check(pyobj)) {
        return JSPyObject_Create(cx, &JSPyCallableClass, proto, pyobj);
    }
    if (PySequence_Check(pyobj)) {
        return JSPyObject_Create(cx, &JSPySequenceClass, proto, pyobj);
    }
    if (PyMapping_Check(pyobj)) {
        return JSPyObject_Create(cx, &JSPyMappingClass, proto, pyobj);
    }
    return JSPyObject_Create(cx, &JSPyObjectClass, proto, pyobj);
}


/*******************************************************************************
 exposed
*******************************************************************************/

bool
JSPyObject_InitPrototype()
{
    JSContext *cx = pyRuntime::GetJSContext();
    if (!cx) {
        return false;
    }
    JSAutoRequest ar(cx);
    gJSPyTypeProto = JS_InitClass(cx, JS_GetGlobalForScopeChain(cx), NULL,
                                  &JSPyTypeClass, NULL, 0,
                                  NULL, JSPyTypeFunctions, NULL, NULL);
    if (!gJSPyTypeProto) {
        return false;
    }
    Py_INCREF(&PyType_Type);
    JS_SetPrivate(gJSPyTypeProto, &PyType_Type);
    return true;
}


bool
JSPyObject_Check(JSObject *jsobj)
{
    if (jsobj == gJSPyTypeProto) {
        return true;
    }
    JSObject *proto = jsobj;
    while ((proto = JS_GetPrototype(proto)) != NULL) {
        if (proto == gJSPyTypeProto) {
            return true;
        }
    }
    return false;
}


JSObject *
JSPyObject_NewOrUsed(JSContext *cx, PyObject *pyobj)
{
    if (!cx || !pyobj) {
        PyErr_BadArgument();
        return NULL;
    }
    if (PyJSObject_Check(pyobj)) {
        return ((PyJSObject *)pyobj)->jsobj;
    }
    JSHolder *holder = NULL;
    if (!pyRuntime::sJSCache->Get(pyobj, &holder)) {
        JSObject *jsobj = JSPyObject_New(cx, pyobj);
        if (!jsobj) {
            return NULL;
        }
        holder = new JSHolder(jsobj);
        if (!holder) {
            return NULL;
        }
        if (!holder->Hold()) {
            delete holder;
            return NULL;
        }
        pyRuntime::sJSCache->Put(pyobj, holder);
    }
    return holder->mJSObj;
}
